기계음·영어 발화 감지 로직 분석

마지막 업데이트 2026-07-29

기계음·영어 발화 감지 로직 분석: 입력: 1. 전체 감지 시스템 개요, 주요 처리 단계: 2. 엔드-투-엔드 데이터 흐름, 결과: 8. 시스템 요약 흐름
동작 흐름 요약
  1. 입력: 1. 전체 감지 시스템 개요
  2. 주요 처리 단계: 2. 엔드-투-엔드 데이터 흐름
  3. 결과: 8. 시스템 요약

apps/web — use-ai-audio-noise-guard.ts / use-ai-english-guard.ts / use-monitor-session.ts  ·  2026-05-20

💬 대화로 먼저 이해하기 — "스피커 감시 경비원 2인조 비유" (비개발자·처음 읽는 사람용)
Q이 문서는 뭘 분석한 건가요?
AAI(핑퐁이)의 스피커 앞을 지키는 경비원 2인조예요. 한 명(Noise Guard)은 소리가 기계처럼 지나치게 균일한지 파형의 리듬(진폭 변동계수 CV + 영교차율 ZCR)을 재고, 다른 한 명(MFCC Guard)은 들리는 발음을 미리 녹음해 둔 영어 발음 지문(템플릿)과 대조(코사인 유사도)합니다.
Q정상 발화도 잠깐은 균일할 수 있잖아요. 오탐은요?
A그래서 즉결 판정을 안 해요. 기계음은 700ms 링 버퍼에서 10/14 프레임 + 200ms 재검증 3/4를 통과해야 하는 2단계 확인이고, 영어는 템플릿이 정해진 순서대로 이어져야 하는 패턴 시퀀스 매칭이에요. 발화 시작 500ms 유예와 10초 쿨다운도 둡니다.
Q잡히면 무슨 일이 일어나요?
A방송을 즉시 중단시키고(cancelResponse) 상황실(치료사 모니터)에 무전(소켓 이벤트)을 쳐요 — Alert 패널 + 토스트로 뜨고, 같은 그룹의 다른 모니터에도 브로드캐스트됩니다.
Q나중에 확인할 근거는 남나요?
A영어 감지 쪽은 CCTV 녹화처럼 턴 단위 MFCC 로그를 JSONL로 S3에 보관해요 — turn_end 후 1.5초 grace를 두고 업로드. 자세한 흐름은 "5. 모니터 측 MFCC 로그 수집 시스템"을 보세요.

1. 전체 감지 시스템 개요

PPI는 AI(핑퐁이)가 비정상 오디오를 출력할 때 이를 감지하는 두 개의 독립적인 방어 시스템을 운영합니다.

🔊 기계음 감지 Noise Guard

AI 오디오 스트림의 진폭 변동계수(CV)영교차율(ZCR)을 분석하여 기계적으로 균일한 파형을 감지합니다.

파일: hooks/use-ai-audio-noise-guard.ts

🗣️ 영어 발화 감지 MFCC Guard

AI 오디오 스트림에서 MFCC 특징을 추출해 사전 녹음된 영어 발음 템플릿과 코사인 유사도를 비교합니다.

파일: hooks/use-ai-english-guard.ts

2. 엔드-투-엔드 데이터 흐름

Guest (아동 브라우저)
AI 오디오 스트림 수신
OpenAI Realtime API → aiAudioStream (MediaStream)
기계음 감지 (병렬 실행)
useAiAudioNoiseGuard
50ms 주기 AnalyserNode 분석 · CV + ZCR 판정 · 2-stage 재검증
+
영어 발화 감지 (병렬 실행)
useAiEnglishGuard
ScriptProcessorNode → MFCC 추출 → 코사인 유사도 → 패턴 시퀀스 매칭
↓   감지 시
Guest Action (기계음)
cancelResponse() 호출 + sendIssueReport()
소켓 이벤트: GUEST_ISSUE_REPORT { issueText: "AI 오디오 기계음 감지 - 자동 중단", icon: "🔊" }
|
Guest Action (영어)
cancelResponse() 호출 + emitMfccEventToMonitor()
소켓 이벤트: GUEST_MFCC_LOG_EVENT { event: { type: "english_detected", sim, phrase, consecutive } }
↓   Socket.io 전달
Socket Server
GROUP_ALERT_BROADCAST 릴레이
같은 그룹 내 다른 세션 모니터들에게도 이벤트 브로드캐스트
Monitor (치료사 브라우저) — use-monitor-session.ts
handleGuestIssueReport / handleMfccLogEvent
이벤트 수신 → AlertType 매핑 → setAlerts + useMonitorDataStore.addAlert
↓   사이드이펙트
모니터 UI
Alert 패널 + Toast 알림
issueToast (3s 자동 닫힘) · alerts[] 배열 추가
+
MFCC 로그 (영어 감지 시)
turn_end 수신 → S3 업로드
JSONL blob → /api/session-logs/{turnId}/mfcc-upload-url → S3 PUT → PATCH mfccS3Key

3. 기계음 감지 알고리즘 상세

파일: apps/web/hooks/use-ai-audio-noise-guard.ts

3-1. 핵심 파라미터

상수설명
ANALYSIS_INTERVAL_MS50분석 주기 (ms)
RING_BUFFER_SIZE141차 판정 링 버퍼 크기 (14프레임 = 700ms)
NOISE_FRAME_THRESHOLD10링 버퍼 중 기계음 판정 필요 프레임 수 (≈71%)
CV_THRESHOLD0.35RMS 변동계수 임계값 (이하 → 기계음 의심)
ZCR_MIN_THRESHOLD0.04영교차율 최소값 (이 이상이어야 기계음 판정 유효)
RMS_SILENCE_FLOOR_DB–50 dB무음 필터 기준 (이하는 판정 제외)
GRACE_PERIOD_MS500AI 발화 시작 후 판정 유예 구간
REVERIFY_DURATION_MS2001차 감지 후 2차 재검증 기간
REVERIFY_FRAMES4재검증 프레임 수
REVERIFY_THRESHOLD3재검증 통과 필요 기계음 프레임 수
COOLDOWN_MS10,000연속 감지 억제 쿨다운 (ms)
RMS_WINDOW_SIZE8CV 계산용 RMS 슬라이딩 윈도우 크기 (400ms)
FFT_SIZE2048AnalyserNode FFT 크기

3-2. 판정 파이프라인

무음 필터
50ms 마다 AnalyserNode에서 Float32Array 읽음. RMS가 –50 dB 미만이면 해당 프레임 건너뜀.
RMS 슬라이딩 윈도우 (8프레임)
최근 8프레임(400ms)의 dB RMS를 linear 변환 후 배열에 유지. CV 계산에 사용.
CV 계산 (변동계수)
CV = std(linear_rms) / mean(linear_rms)
정상 음성 CV ≈ 0.85 · 기계음 CV ≈ 0.27. 임계값 0.35 이하이면 기계음 후보.
ZCR 보조 조건
영교차율이 0.04 이상이어야 판정 유효. 너무 매끈한 저주파 구간의 오탐을 보완.
1차 판정 — 링 버퍼
14프레임 원형 버퍼에 기계음 여부 Boolean 기록. 10/14 이상이면 후보 감지.
2차 재검증 (200ms / 4프레임)
후보 감지 후 200ms 추가 관찰. 4프레임 중 3프레임 이상 기계음이면 확정 감지. 아니면 링 버퍼 초기화 후 재시작.
쿨다운 체크 (10초)
마지막 감지로부터 10초 이내면 콜백 호출 억제.
onNoiseDetected() 콜백
cancelResponse() 호출 → AI 응답 즉시 중단.
guestSocket.sendIssueReport("AI 오디오 기계음 감지 - 자동 중단", "🔊") 전송.
설계 의도: 리뷰 반영으로 1차 감지 즉시 중단 대신 200ms 재검증을 추가. 짧은 무음 dip이나 발화 초기 과도기(500ms 유예)에서 발생하는 오탐을 줄이기 위한 구조.

4. 영어 발화 감지 알고리즘 상세 (MFCC)

파일: apps/web/hooks/use-ai-english-guard.ts + apps/web/lib/mfcc.ts

4-1. MFCC 추출 파라미터

파라미터설명
SAMPLE_RATE16,000 Hz처리 샘플레이트 (입력을 여기로 다운샘플)
FRAME_SIZE400 samples25ms @ 16kHz
HOP_SIZE160 samples10ms 스텝
FFT_SIZE512Radix-2 Cooley-Tukey FFT
NUM_MEL_FILTERS26Mel 필터뱅크 수
NUM_MFCC13MFCC 계수 수 (c0~c12)
PRE_EMPHASIS0.97고주파 강조 계수

4-2. 슬라이딩 윈도우 설정

상수설명
WINDOW_SAMPLES_16K4,800300ms @ 16kHz 분석 윈도우
HOP_SAMPLES_16K2,400150ms 슬라이딩 간격
SCRIPT_PROCESSOR_BUFFER_SIZE4,096ScriptProcessorNode 버퍼
MIN_RMS_ENERGY0.005무음 구간 스킵 기준
GRACE_PERIOD_MS500AI 발화 시작 후 유예 구간
COOLDOWN_MS10,000재감지 억제 쿨다운
TOP_N_TEMPLATES3검증 로그에 포함할 상위 유사도 템플릿 수
MAX_SILENCE_HOPS_FOR_RESET5연속 무음 hop 한계 (≈750ms 까지 커서 유지)
PATTERN_RULE_MISS_TOLERANCE2패턴 시퀀스 miss 허용 횟수

4-3. 감지 알고리즘 — 두 가지 모드

모드 A: 패턴 시퀀스 매칭 주 감지 방식
mfccPatternRules 존재 시 활성화
서버에서 내려온 MfccPatternRule[]로 순서 있는 템플릿 시퀀스 정의
각 rule에 cursor 유지
ruleCursorsRef: Map<ruleId, cursor> — 현재 몇 번째 스텝 대기 중인지 추적
스텝 매칭 조건
maxSimilarity ≥ 전역 threshold AND bestTemplateIdx === 기대 tldx AND sim ≥ 스텝별 threshold
miss 허용 (PATTERN_RULE_MISS_TOLERANCE = 2)
2회 이상 miss 시 cursor 리셋. 음절 경계 dip에서 시퀀스가 중단되지 않도록 보완.
시퀀스 완성 + stopSpeech=true
onEnglishDetected(tldxSequence) 호출 → AI 응답 즉시 중단
모드 B: Consecutive 매칭 로그 전용
패턴 규칙 없을 때 fallback
연속 매칭 카운터(consecutiveMatchCount)로 판단
같은 phraseGroup 내에서만 누적
그룹이 바뀌거나 offset이 감소하면 카운터 리셋
threshold 도달 시 로그만 남김
※ 현재는 action 없음 — 패턴 시퀀스 감지가 주 트리거

4-4. MFCC 추출 파이프라인

PCM 입력 (임의 SR)
16kHz 다운샘플 (선형 보간)
Pre-emphasis (α=0.97)
Hamming 윈도우 (25ms)
FFT-512 (Radix-2)
파워 스펙트럼
Mel 필터뱅크 26개
log(E)
DCT-II → 13 계수
c0 제거 후 코사인 유사도
c0(에너지 계수) 제거 이유: MFCC의 첫 번째 계수(c0)는 모든 음성에서 비슷한 에너지 레벨을 가져 오탐을 유발합니다. c1~c12만 비교해 음색 유사도를 더 정확하게 측정합니다.

5. 모니터 측 MFCC 로그 수집 시스템

파일: entities/monitor-session/model/use-monitor-session.ts + hooks/use-mfcc-log-collector.ts

5-1. 턴 버퍼 구조

버퍼 관련 상수
상수
MONITOR_MFCC_MAX_EVENTS_PER_TURN2,000
MFCC_TURN_END_FLUSH_GRACE_MS1,500 ms
MFCC_TURN_END_FLUSH_INFLIGHT_POLL_MS200 ms
MFCC_TURN_END_FLUSH_MAX_WAIT_MS6,000 ms
핵심 Ref 변수
변수설명
currentTurnBufferRef현재 턴 이벤트 버퍼 (MfccLogEvent[])
inActiveTurnRefai_speaking_start ~ turn_end 구간 flag
currentTurnUserIdRef현재 턴 소유 userId
pendingTurnFlushRefgrace 지연 flush 타이머 정보
mfccUploadInProgressRef중복 업로드 방지 Set<turnId>

5-2. 이벤트 타입 분류 (MfccLogEvent)

type설명소비 방식
session_start세션 시작 메타 정보버퍼 append
session_end세션 종료버퍼 append
ai_speaking_startAI 발화 시작 → 새 턴 시작이전 미완결 버퍼 flush + inActiveTurn=true
ai_speaking_stopAI 발화 중단버퍼 append
transcript발화 전사 스냅샷버퍼 append
mfcc_sample300ms 윈도우 MFCC + 코사인 유사도버퍼 append
match_candidate연속 매칭 후보 (로그용)버퍼 append
english_detected영어 발화 최종 감지버퍼 append + onMfccEnglishDetected 콜백
pattern_step패턴 시퀀스 진행 스텝버퍼 append
resetconsecutive 카운터 리셋 이유버퍼 append
turn_end턴 종료 → grace flush 스케줄grace 타이머 시작 후 S3 업로드

5-3. turn_end 처리 — Grace Flush 흐름

소켓 이벤트
turn_end 수신 (GUEST_MFCC_LOG_EVENT)
처리 1
이전 pendingFlush 즉시 실행
이전 턴이 grace 중이었다면 clearTimeout 후 즉시 flushTurnMfcc 호출
처리 2
Grace 타이머 시작 (1,500ms)
inActiveTurn 유지 — 그 사이 도착하는 이벤트도 현재 버퍼에 수용
↓   1,500ms 후 (또는 다음 ai_speaking_start 시 즉시)
Inflight 폴링
getInflightLidDetection() 체크
Whisper 추론이 in-flight면 200ms 간격으로 재폴링. 최대 6,000ms 하드 캡.
↓   inflight 완료 또는 하드 캡
S3 업로드
uploadTurnMfccLog
/api/session-logs/{turnId}/mfcc-upload-url → Presigned URL → S3 PUT (JSONL)
DB 연결
registerTurnMfccLog
/api/session-logs/{turnId}/mfcc PATCH — mfccS3Key, mfccRange 업데이트

5-4. mfccRange 요약 메타 (summarizeTurnMfcc)

필드설명
startMs / endMs턴 이벤트 타임스탬프 범위
eventCount총 이벤트 수
sampleCountmfcc_sample 이벤트 수
detectedEnglishenglish_detected 이벤트 존재 여부
detectedByMfccdetectedEnglish와 동일
maxSimmfcc_sample 또는 english_detected 중 최대 코사인 유사도
matchedPatternSequencepattern_step에서 완성된 tldx 시퀀스 배열

6. 모니터 수신 처리 — Alert 생성

6-1. 이벤트 → AlertType 매핑

🔊
GUEST_ISSUE_REPORT → issueText.includes("기계음 감지")
AlertType.AI_MECHANICAL_NOISE · 우선순위 P0
즉시 토스트
🗣️
GUEST_ISSUE_REPORT → issueText.includes("영어 발화 감지")
AlertType.AI_ENGLISH_DETECTED · 우선순위 P0
즉시 토스트
🧩
GUEST_ISSUE_REPORT → issueText.includes("영어 발화 패턴 감지")
AlertType.MFCC_PATTERN_MATCHING · 우선순위 P0
즉시 토스트
🆘
GUEST_ISSUE_REPORT → 기타 issueText
AlertType.CHILD_HELP_REQUEST · 우선순위 P0
즉시 토스트

6-2. MFCC english_detected 이벤트 처리 흐름

handleMfccLogEvent 수신
roomId 필터링 후 event.type 분기
event.type === "ai_speaking_start"
inActiveTurn = true. 이전 pendingFlush가 있으면 즉시 실행.
event.type === "english_detected"
onMfccEnglishDetectedRef.current({ sim, phrase, consecutive }) 콜백 호출. 버퍼에도 append.
event.type === "turn_end"
Grace 타이머 스케줄. inActiveTurn = false 는 flush 완료 후 처리.

6-3. 중복 감지 방지 메커니즘

mountedMonitorRoomIds

카드뷰에서 같은 그룹의 여러 세션이 동시 마운트될 때 GROUP_ALERT_BROADCAST 중복 처리를 방지하는 모듈-레벨 Set. 원본 roomId를 담당하는 카드가 이미 직접 처리하므로 브로드캐스트 경로에서 skip.

mfccUploadInProgressRef

Set<turnId>로 동일 turnId에 대한 동시 S3 업로드를 방지. flushTurnMfcc 진입 시 체크하고, finally에서 삭제.

7. 연결 해제 시 버퍼 정리

guest-disconnected / peer-left 수신 시: pendingTurnFlushRef 타이머를 clearTimeout하고 currentTurnBufferRef를 비웁니다. turn_end 없이 끊어진 미완결 턴은 S3에 업로드하지 않고 폐기합니다 (튜닝 가치가 낮고, 다음 세션 시작 시 버퍼 오염 방지 목적).
컴포넌트 unmount 시: pendingTurnFlushRef 타이머만 clearTimeout합니다. 남은 버퍼는 폐기되며 S3 업로드 호출을 생략합니다.

8. 시스템 요약

항목 기계음 감지 (NoiseGuard) 영어 발화 감지 (MFCC Guard)
감지 원리 RMS CV < 0.35 + ZCR 조건 MFCC 코사인 유사도 + 패턴 시퀀스
분석 주기 50ms (AnalyserNode) 150ms 슬라이딩 hop (ScriptProcessor)
감지 확인 방식 2-stage (링 버퍼 + 재검증 200ms) 패턴 시퀀스 완성 (miss 2회 허용)
AI 응답 중단 cancelResponse() 즉시 호출 onEnglishDetected → cancelResponse()
소켓 이벤트 GUEST_ISSUE_REPORT GUEST_MFCC_LOG_EVENT
모니터 AlertType AI_MECHANICAL_NOISE (P0) AI_ENGLISH_DETECTED / MFCC_PATTERN_MATCHING (P0)
로그 저장 없음 JSONL → S3 (turn_end grace flush)
쿨다운 10초 10초
발화 시작 유예 500ms 500ms

생성: 2026-05-20  ·  분석 대상: apps/web (develop 브랜치)  ·  주요 파일: use-ai-audio-noise-guard.ts, use-ai-english-guard.ts, use-monitor-session.ts, use-mfcc-log-collector.ts, lib/mfcc.ts